package com.iplay.service.query.specification;

import java.util.Collection;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.springframework.data.jpa.domain.Specification;

import com.iplay.service.query.Query;
import com.iplay.service.query.QueryCondition;
import com.iplay.service.query.exception.IllegalQueryTreeException;
import com.iplay.service.query.Query.LogicalOperator;
import com.iplay.service.query.Query.QueryTreeNode;

public class QuerySepcification<T> implements Specification<T> {

	private Query query;

	public QuerySepcification(Query query) {
		this.query = query;
	}

	@Override
	public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
		return toPredicate(root, cb, query.buildQueryTree());
	}

	private Predicate toPredicate(Root<T> root, CriteriaBuilder cb,
			QueryTreeNode node) {
		if(node==null)
			return cb.and();
		if(node.getLeft()==null&&node.getRight()==null) {
			if(node.getValue() instanceof QueryCondition) {
				return toPredicate(root, cb, QueryCondition.class.cast(node.getValue()));
			}
			throw new IllegalQueryTreeException("Leaf must be a query condition!");
		}else if(node.getValue() instanceof LogicalOperator) {
			LogicalOperator op = LogicalOperator.class.cast(node.getValue());
			switch (op) {
			case AND:
				return cb.and(toPredicate(root, cb, node.getLeft()), toPredicate(root, cb, node.getRight()));
			case OR:
				return cb.or(toPredicate(root, cb, node.getLeft()), toPredicate(root, cb, node.getRight()));
			}
		}
		throw new IllegalQueryTreeException("Father node must be a logical operator!");
	}

	private Predicate toPredicate(Root<T> root, CriteriaBuilder cb, QueryCondition con) {
		Predicate predicate = null;
		switch(con.getOperator()){
		case EQ:
			predicate = cb.equal(root.get(con.getKey()), con.getValue());
			break;
		case GE:
			predicate = cb.ge(root.get(con.getKey()), (Number)con.getValue());
			break;
		case GT:
			predicate = cb.gt(root.get(con.getKey()), (Number)con.getValue());
			break;
		case IN:
			predicate = root.get(con.getKey()).in((Collection<?>)con.getValue());
			break;
		case LE:
			predicate = cb.le(root.get(con.getKey()), (Number)con.getValue());
			break;
		case LT:
			predicate = cb.lt(root.get(con.getKey()), (Number)con.getValue());
			break;
		case LIKE:
			predicate = cb.like(root.get(con.getKey()), String.class.cast(con.getValue()));
			break;
		}
		return predicate;
	}

}
